/*******************************************************************************
 *
 *    Copyright (c) Baina Info Tech Co. Ltd
 *
 *    Snappy
 * 
 *    StringUtil
 *
 *    @author: chzhong
 *    @since:  2010-6-9
 *    @version: 1.0
 *
 ******************************************************************************/

package com.sdk.util;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.http.protocol.HTTP;
import org.json.JSONObject;

import android.content.Context;
import android.content.res.AssetManager;
import android.net.Uri;
import android.text.TextUtils;
import android.text.format.Formatter;

import com.sdk.formatter.BitUtil;

/**
 * 文本工具类。
 * 
 * @author chzhong
 * 
 */
public final class StringUtil {

	public static final int MAX_COMMENT_LENGTH = 1000;
	public static final int MAX_PASSWORD_LENGTH = 100;
	public static final int MIN_PASSWORD_LENGTH = 6;
	private static final Pattern NICK_NAME_PATTERN = Pattern.compile(
			"^[_A-Za-z][_A-Za-z0-9]{2,14}$", Pattern.CASE_INSENSITIVE
					| Pattern.UNICODE_CASE);
	private static final Pattern NICK_NAME_RESERVED_PATTERN = Pattern
			.compile(
					".*Tapler.*|.*Admin.*|.*System.*|.*Mobo\\W*Tap.*|.*Mobo\\W*Square.*",
					Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);

	private static final Pattern EMAIL_PATTERN = Pattern.compile(
			"^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}$",
			Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);

	private static final long ONE_GB = 1L << 30;
	private static final long ONE_MB = 1L << 20;
	private static final long ONE_KB = 1L << 10;

	private static final long GB_UNIT_THREDHOLD = 1000000000;
	private static final long MB_UNIT_THREDHOLD = 1000000;
	private static final long KB_UNIT_THREDHOLD = 1000;

	private static final String GB_UNIT = "G";
	private static final String MB_UNIT = "M";
	private static final String KB_UNIT = "K";
	private static final String BYTES_UNIT = "Byte(s)";

	private static final String SIZE_FORMAT = "%,.2f %s";
	private static final String SIZE_FORMAT_WITHOUT_DIGIT = "%,.0f %s";

	public static final String LOGO_URL = "http://www.mobosquare.com/developer/features/newsletter";

	/**
	 * The max length for file name.
	 */
	public static final int MAX_PATH = 256;

	/**
	 * Illegal file name chars.
	 */
	public static final Pattern ILLEGAL_FILE_NAME_CHARS = Pattern
			.compile("[\\\\/:*?<>|]+");

	/**
	 * 空白字符。
	 */
	private static final char[] WhitespaceChars = new char[] { '\u0000',
			'\u0001', '\u0002', '\u0003', '\u0004', '\u0005', '\u0006',
			'\u0007', '\u0008', '\u0009', '\n', '\u000b', '\u000c', '\r',
			'\u000e', '\u000f', '\u0010', '\u0011', '\u0012', '\u0013',
			'\u0014', '\u0015', '\u0016', '\u0017', '\u0018', '\u0019',
			'\u001a', '\u001b', '\u001c', '\u001d', '\u001e', '\u001f',
			'\u0020'

	};
	private static final String TAG = "StringUtil";

	private static final int BUFFER_SIZE = 4096;

	/**
	 * 判断给定的字符数组（已排序）中是否包含给定的字符。
	 * 
	 * @param chars
	 *            已经排序的字符数组。
	 * @param ch
	 *            要检查的字符。
	 * @return 如果 ch 存在于 chars 中则返回 true；否则返回 false。
	 */
	public static final boolean containsChar(final char[] chars, final char ch) {
		return Arrays.binarySearch(chars, ch) >= 0;
	}

	/**
	 * 返回参数中第一个非 {@code null} 的参数。
	 * 
	 * @param args
	 *            要检查的参数。
	 * @return 给定的参数中第一个非 {@code null} 的参数；如果参数都为 {@code null}，则返回 {@code null}。
	 */
	public static <T> T firstNotNull(final T... args) {
		for (final T object : args) {
			if (object != null) {
				return object;
			}
		}
		return null;
	}

	public static String dbUnescape(String src) {
		if (src == null) {
			return "";
		}
		src = src.replaceAll("''*", "''");// change all ''... to ''
		return src;
	}

	/**
	 * 返回参数中第一个非 {@code null} 的参数。
	 * 
	 * @param args
	 *            要检查的参数。
	 * @return 给定的参数中第一个非 {@code null} 的参数；如果参数都为 {@code null}，则返回 {@code null}。
	 */
	public static <T extends CharSequence> T firstNotEmpty(final T... args) {
		for (final T object : args) {
			if (!TextUtils.isEmpty(object)) {
				return object;
			}
		}
		return null;
	}

	/**
	 * 返回参数中第一个非零的参数。
	 * 
	 * @param args
	 *            要检查的参数。
	 * @return 给定的参数中第一个非零的参数；如果参数都为零，则返回零。
	 */
	public static int firstNonZeroInt(final int... args) {
		for (final int object : args) {
			if (object != 0) {
				return object;
			}
		}
		return 0;
	}

	/**
	 * 返回参数中第一个非负数的参数。
	 * 
	 * @param args
	 *            要检查的参数。
	 * @return 给定的参数中第一个非负数；如果参数都为负数，则返回 {@linkplain Integer#MIN_VALUE}。
	 */
	public static int firstNonNegativeInt(final int... args) {
		for (final int object : args) {
			if (object >= 0) {
				return object;
			}
		}
		return Integer.MIN_VALUE;
	}

	/**
	 * 返回参数中第一个正数的参数。
	 * 
	 * @param args
	 *            要检查的参数。
	 * @return 给定的参数中第一个正数；如果参数都为负数或零，则返回零。
	 */
	public static int firstPostiveInt(final int... args) {
		for (final int object : args) {
			if (object > 0) {
				return object;
			}
		}
		return 0;
	}

	/**
	 * 返回参数中第一个非零的参数。
	 * 
	 * @param args
	 *            要检查的参数。
	 * @return 给定的参数中第一个非零的参数；如果参数都为零，则返回零。
	 */
	public static long firstNonZeroLong(final long... args) {
		for (final long object : args) {
			if (object != 0) {
				return object;
			}
		}
		return 0;
	}

	/**
	 * 返回参数中第一个非负数的参数。
	 * 
	 * @param args
	 *            要检查的参数。
	 * @return 给定的参数中第一个非负数；如果参数都为负数，则返回 {@linkplain longeger#MIN_VALUE}。
	 */
	public static long firstNonNegativeLong(final long... args) {
		for (final long object : args) {
			if (object >= 0) {
				return object;
			}
		}
		return Long.MIN_VALUE;
	}

	/**
	 * 返回参数中第一个正数的参数。
	 * 
	 * @param args
	 *            要检查的参数。
	 * @return 给定的参数中第一个正数；如果参数都为负数或零，则返回零。
	 */
	public static long firstPostiveLong(final long... args) {
		for (final long object : args) {
			if (object > 0) {
				return object;
			}
		}
		return 0;
	}

	/**
	 * 返回参数中第一个非零的参数。
	 * 
	 * @param args
	 *            要检查的参数。
	 * @return 给定的参数中第一个非零的参数；如果参数都为零，则返回零。
	 */
	public static double firstNonZero(final double... args) {
		for (final double object : args) {
			if (object != 0) {
				return object;
			}
		}
		return 0;
	}

	/**
	 * 返回参数中第一个数的参数。
	 * 
	 * @param args
	 *            要检查的参数。
	 * @return 给定的参数中第一个数；如果参数都为 {@linkplain Double#NaN}，则返回
	 *         {@linkplain Double#NaN}。
	 */
	public static double firstDouble(final double... args) {
		for (final double object : args) {
			if (!Double.isNaN(object)) {
				return object;
			}
		}
		return Double.NaN;
	}

	/**
	 * 返回参数中第一个有穷数的参数。
	 * 
	 * @param args
	 *            要检查的参数。
	 * @return 给定的参数中第一个有穷数；如果参数都为 {@linkplain Double#NaN} 或者无穷，则返回
	 *         {@linkplain Double#NaN}。
	 */
	public static double firstFinite(final double... args) {
		for (final double object : args) {
			if (!Double.isNaN(object) && !Double.isInfinite(object)) {
				return object;
			}
		}
		return Double.NaN;
	}

	/**
	 * 返回参数中第一个非负数的参数。
	 * 
	 * @param args
	 *            要检查的参数。
	 * @return 给定的参数中第一个非负数；如果参数都为负数，则返回 {@linkplain Double#NaN}。
	 */
	public static double firstNonNegative(final double... args) {
		for (final double object : args) {
			if (object >= 0) {
				return object;
			}
		}
		return Double.NaN;
	}

	/**
	 * 返回参数中第一个正数的参数。
	 * 
	 * @param args
	 *            要检查的参数。
	 * @return 给定的参数中第一个正数；如果参数都为负数或零，则返回零。
	 */
	public static double firstPostive(final double... args) {
		for (final double object : args) {
			if (object > 0) {
				return object;
			}
		}
		return 0;
	}

	/**
	 * 将给定的时间格式化为 XSD/SOAP 的格式。
	 * 
	 * @param date
	 *            要格式化的日期。
	 * @return 该日期的 yyyy-MM-ddTHH:mm:ss+ZZ:ZZ 的格式。
	 * @deprecated 请使用
	 *             {@link com.mobosquare.util.mgeek.android.util.XmlUtil#dateToXsd(Date)}
	 *             ，它提供了相同的功能。 方法。
	 */
	@Deprecated
	public static final String formatDate(final Date date) {
		final String dateTime = String.format("%1$tFT%1$tT", date);
		final String zone = String.format("%1$tz", date);
		return dateTime + zone.substring(0, 3) + ":" + zone.substring(3);
	}

	/**
	 * 获取一个类完全限定名的简单名。
	 * 
	 * @param className
	 *            类的完全限定名。
	 * @return className 的简单名。
	 */
	public static final String getSimpleClassName(final String className) {
		if (isNullOrEmpty(className)) {
			return className;
		}
		final int index = className.lastIndexOf('.');
		if (-1 == index) {
			return className.substring(index);
		}
		return className;
	}

	/**
	 * 获取异常信息的堆栈跟踪信息。
	 * 
	 * @param throwable
	 *            要解析的异常信息。
	 * @return 该异常信息的堆栈跟踪信息。
	 */
	public static final String getThrowableStackTrace(final Throwable throwable) {
		final StringWriter sWriter = new StringWriter();
		final PrintWriter printWriter = new PrintWriter(sWriter);
		throwable.printStackTrace(printWriter);
		printWriter.flush();
		printWriter.close();
		final String message = sWriter.toString();
		return message;
	}

	/**
	 * 获取给定字符串中，某些特定字符的第一个索引。
	 * 
	 * @param value
	 *            要搜索的字符串。
	 * @param chars
	 *            要查找的字符。
	 * @return value 中第一个出现的 chars 中任意字符的索引。 如果 value 中不含有 chars 中的任意字符，则返回 -1。
	 */
	public static int indexOfAny(final String value, final char... chars) {
		return indexOfAny(value, 0, chars);
	}

	/**
	 * 获取给定字符串中，某些特定字符的第一个索引。
	 * 
	 * @param value
	 *            要搜索的字符串。
	 * @param chars
	 *            要查找的字符。
	 * @param start
	 *            查找的起始点。
	 * @return value 中，从 start 起第一个出现的 chars 中任意字符的索引。 如果 value 中不含有 chars
	 *         中的任意字符，则返回 -1。
	 */
	public static int indexOfAny(final String value, final int start,
			final char... chars) {
		if (null == value || value.length() == 0) {
			return -1;
		}
		final int i = 0, n = chars.length;
		int index = -1;
		while (i < n || -1 == index) {
			index = value.indexOf(chars[i], start);
		}
		return index;
	}

	/**
	 * 判断一个字符串是否为 ""。
	 * 
	 * @param 待检查的字符串
	 *            。
	 * @return 如果字符串为 ""，则返回 true；否则返回 false。
	 */
	public static final boolean isEmpty(final String value) {
		return null != value && 0 == value.length();
	}

	/**
	 * 判断一个字符串是否为 null 或者 ""。
	 * 
	 * @param 待检查的字符串
	 *            。
	 * @return 如果字符串为 null 或者 ""，则返回 true；否则返回 false。
	 */
	public static final boolean isNullOrEmpty(final String value) {
		return TextUtils.isEmpty(value);
	}

	/**
	 * 判断一个字符串是否为 null 或者只包含空白字符（空格、回车、换行等）。
	 * 
	 * @param 待检查的字符串
	 *            。
	 * @return 如果字符串为 null 或者只包含空白字符，则返回 true；否则返回 false。
	 */
	public static final boolean isNullOrWhitespaces(final String value) {
		return null == value || 0 == value.trim().length();
	}

	/**
	 * 判断一个字符串是否只包含空白字符（空格、回车、换行等）。
	 * 
	 * @param 待检查的字符串
	 *            。
	 * @return 如果字符串只包含空白字符，则返回 true；否则返回 false。
	 */
	public static final boolean isWhitespaces(final String value) {
		return null != value && 0 == value.trim().length();
	}

	/**
	 * 获取给定字符串中，某些特定字符的最后一个索引。
	 * 
	 * @param value
	 *            要搜索的字符串。
	 * @param chars
	 *            要查找的字符。
	 * @return value 中最后一个出现的 chars 中任意字符的索引。 如果 value 中不含有 chars 中的任意字符，则返回 -1。
	 */
	public static final int lastIndexOfAny(final String value,
			final char... chars) {
		return lastIndexOfAny(value, value.length() - 1, chars);
	}

	/**
	 * 获取给定字符串中，某些特定字符的最后一个索引。
	 * 
	 * @param value
	 *            要搜索的字符串。
	 * @param chars
	 *            要查找的字符。
	 * @param start
	 *            查找的起始点，从这个索引开始向前搜索。
	 * @return value 中，从 start 起最后一个出现的 chars 中任意字符的索引。 如果 value 中不含有 chars
	 *         中的任意字符，则返回 -1。
	 */
	public static final int lastIndexOfAny(final String value, final int start,
			final char... chars) {
		if (null == value || value.length() == 0) {
			return -1;
		}
		final int i = 0, n = chars.length;
		int index = -1;
		while (i < n || -1 == index) {
			index = value.lastIndexOf(chars[i], start);
		}
		return index;
	}

	/**
	 * 规范化字符串。
	 * 
	 * @param s
	 *            要规范化的字符串。
	 * @return 如果字符串为 null 或者长度大于零，则返回 s；如果字符串长度为零，则返回 null。
	 */
	public final static String normalize(final String s) {
		if (s == null || s.length() > 0) {
			return s;
		}
		return null;
	}

	/**
	 * 以 UTF-8 编码读取一个 Assert 文件的内容。
	 * 
	 * @param context
	 *            当前应用程序的上下文。
	 * @param fileName
	 *            要读取的 Assert 文件名。
	 * @return 如果成功，返回 Assert 文件的内容；否则返回 null。
	 */
	public static String stringFromAssert(final Context context,
			final String fileName) {
		String content = null;
		try {
			final AssetManager asserts = context.getAssets();
			final InputStream myInput = asserts.open(fileName);
			final byte[] boe = new byte[3];
			myInput.read(boe, 0, boe.length);
			if (!BitUtil.boeIsUtf8(boe)) {
				myInput.reset();
			}
			content = StringUtil.stringFromInputStream(myInput, "utf8");
			myInput.close();
		} catch (final Exception e) {
			e.printStackTrace();
			Log.e(TAG, "Error reading string from assert.");
		}
		return content;
	}

	/**
	 * 以给定的编码读取一个 Assert 文件的内容。
	 * 
	 * @param context
	 *            当前应用程序的上下文。
	 * @param fileName
	 *            要读取的 Assert 文件名。
	 * @param encoding
	 *            文件的编码方式。如果文件有 BOE，则根据 BOE 确定编码。
	 * @return 如果成功，返回 Assert 文件的内容；否则返回 null。
	 */
	public static String stringFromAssert(final Context context,
			final String fileName, String encoding) {
		String content = null;
		try {
			final AssetManager asserts = context.getAssets();
			final InputStream myInput = asserts.open(fileName);
			final byte[] boe = new byte[3];
			myInput.read(boe, 0, boe.length);
			if (BitUtil.boeIsUtf8(boe)) {
				encoding = "utf8";
				myInput.reset();
			} else if (BitUtil.boeIsUnicode(boe)) {
				encoding = "Unicode";
				myInput.reset();
				myInput.skip(2);
			}
			content = StringUtil.stringFromInputStream(myInput, encoding);
			myInput.close();
		} catch (final Exception e) {
			e.printStackTrace();
			Log.e(TAG, "Error reading string from assert.");
		}
		return content;
	}

	/**
	 * 读取缓存读取器中的内容。
	 * 
	 * @param bufferedReader
	 *            要读取的缓冲读取器。
	 * @return 存读取器中的内容。
	 */
	public static String stringFromBufferedReader(
			final BufferedReader bufferedReader) {
		if (bufferedReader == null) {
			return null;
		}
		final StringBuffer result = new StringBuffer();
		String line = null;
		try {
			while ((line = bufferedReader.readLine()) != null) {
				result.append(line);
			}
		} catch (final IOException e) {
			e.printStackTrace();
			return null;
		}
		return result.toString();
	}

	/**
	 * 读取一个输入流中的内容，并构造成字符串。
	 * 
	 * @param inputStream
	 *            要读取的输入流。
	 * @param encoding
	 *            流采用的字符编码。
	 * @return 流中内容所表示的字符串。
	 * @throws UnsupportedEncodingException
	 *             不支持此编码。
	 */
	public static String stringFromInputStream(final InputStream inputStream,
			final String encoding) throws UnsupportedEncodingException {
		final InputStreamReader reader = new InputStreamReader(inputStream,
				encoding);
		return stringFromBufferedReader(new BufferedReader(reader));
	}

	/**
	 * 移除给定字符串开头和结尾处的特定字符。
	 * 
	 * @param value
	 *            要修剪的字符串。
	 * @param chars
	 *            要移除的字符。
	 * @return 修剪后的字符串。
	 */
	public static String trim(final String value, final char... chars) {
		if (null == value || value.length() == 0) {
			return value;
		}
		Arrays.sort(chars);
		int startIndex = 0;
		int endIndex = value.length() - 1;
		boolean flag = containsChar(chars, value.charAt(startIndex));
		while (flag && startIndex <= endIndex) {
			startIndex++;
			flag = containsChar(chars, value.charAt(startIndex));
		}

		flag = containsChar(chars, value.charAt(endIndex));
		while (flag && startIndex <= endIndex) {
			endIndex--;
			flag = containsChar(chars, value.charAt(endIndex));
		}
		if (startIndex >= endIndex) {
			return "";
		}
		return value.substring(startIndex, endIndex + 1);
	}

	/**
	 * 移除给定字符串结尾的空白字符。
	 * 
	 * @param value
	 *            要修剪的字符串。
	 * @return 修剪后的字符串。
	 */
	public static final String trimEnd(final String value) {
		return trimEnd(value, WhitespaceChars);
	}

	/**
	 * 移除给定字符串结尾处的特定字符。
	 * 
	 * @param value
	 *            要修剪的字符串。
	 * @param chars
	 *            要移除的字符。
	 * @return 修剪后的字符串。
	 */
	public static String trimEnd(final String value, final char... chars) {
		if (null == value || value.length() == 0) {
			return value;
		}
		Arrays.sort(chars);
		int endIndex = value.length() - 1;
		boolean flag = containsChar(chars, value.charAt(endIndex));
		while (flag) {
			endIndex--;
			flag = containsChar(chars, value.charAt(endIndex));
		}
		if (0 >= endIndex) {
			return "";
		}
		return value.substring(0, endIndex + 1);
	}

	/**
	 * 移除给定字符串开头的空白字符。
	 * 
	 * @param value
	 *            要修剪的字符串。
	 * @return 修剪后的字符串。
	 */
	public static final String trimStart(final String value) {
		return trimStart(value, WhitespaceChars);
	}

	/**
	 * 移除给定字符串开头的特定字符。
	 * 
	 * @param value
	 *            要修剪的字符串。
	 * @param chars
	 *            要移除的字符。
	 * @return 修剪后的字符串。
	 */
	public static String trimStart(final String value, final char... chars) {
		if (null == value || value.length() == 0) {
			return value;
		}
		Arrays.sort(chars);
		int startIndex = 0;
		boolean flag = containsChar(chars, value.charAt(startIndex));
		while (flag) {
			startIndex++;
			flag = containsChar(chars, value.charAt(startIndex));
		}
		if (startIndex >= value.length()) {
			return "";
		}
		return value.substring(startIndex);
	}

	private StringUtil() {
	}

	/**
	 * Convert an {@link InputStream} to String.
	 * 
	 * @param stream
	 *            the stream that contains data.
	 * @param encoding
	 *            the encoding of the data.
	 * @return the result string.
	 * @throws IOException
	 *             an I/O error occurred.
	 */
	public static String stringFromInputStream2(final InputStream stream,
			String encoding) throws IOException {
		if (null == stream) {
			throw new IllegalArgumentException("stream may not be null.");
		}
		if (TextUtils.isEmpty(encoding)) {
			encoding = System.getProperty("file.encoding", "utf-8");
		}
		String result;
		final InputStreamReader reader = new InputStreamReader(stream, encoding);
		final StringWriter writer = new StringWriter();
		final char[] buffer = new char[BUFFER_SIZE];
		int charRead = reader.read(buffer);
		while (charRead > 0) {
			writer.write(buffer, 0, charRead);
			charRead = reader.read(buffer);
		}
		result = writer.toString();
		return result;
	}

	/**
	 * Convert an {@link InputStream} to String.
	 * 
	 * @param stream
	 *            the stream that contains data.
	 * @return the result string.
	 * @throws IOException
	 *             an I/O error occurred.
	 */
	public static String stringFromInputStream2(final InputStream stream)
			throws IOException {
		return stringFromInputStream2(stream, "utf-8");
	}

	/**
	 * Tiny a given string, return a valid file name.
	 * 
	 * @param fileName
	 *            the file name to clean.
	 * @return the valid file name.
	 */
	public static String safeFileName(String fileName) {
		if (TextUtils.isEmpty(fileName)) {
			return "";
		}
		if (fileName.length() > MAX_PATH) {
			fileName = fileName.substring(0, MAX_PATH);
		}
		final Matcher matcher = ILLEGAL_FILE_NAME_CHARS.matcher(fileName);
		fileName = matcher.replaceAll("_");
		return fileName;
	}

	/**
	 * Html-encode the string.
	 * 
	 * @param s
	 *            the string to be encoded
	 * @return the encoded string
	 */
	public static String htmldecode(String text) {
		text = text.replaceAll("&amp;", "&");
		text = text.replaceAll("&lt;", "<");
		text = text.replaceAll("&gt;", ">");
		text = text.replaceAll("&quot;", "\"");
		text = text.replaceAll("&apos;", "\'");
		return text;
	}

	public static final String optString(final JSONObject object,
			final String key) {
		final Object o = object.opt(key);
		return !JSONObject.NULL.equals(o) ? o.toString() : null;
	}

	/**
	 * Error message presented when a user tries to treat an opaque URI as
	 * hierarchical.
	 */
	private static final String NOT_HIERARCHICAL = "This isn't a hierarchical URI.";

	/**
	 * Get query parameter from {@linkplain Uri} correctly.
	 * 
	 * @param uri
	 *            the {@linkplain Uri}
	 * @param key
	 *            the query key.
	 * @return the decode query value of with key, or {@code null} if not found.
	 */
	public static final String getQueryParameter(final Uri uri, final String key) {
		if (null == uri) {
			throw new NullPointerException("uri");
		}
		if (uri.isOpaque()) {
			throw new UnsupportedOperationException(NOT_HIERARCHICAL);
		}
		if (key == null) {
			throw new NullPointerException("key");
		}

		final String query = uri.getEncodedQuery();
		if (query == null) {
			return null;
		}

		final String encodedKey = Uri.encode(key, null);
		final int encodedKeyLength = encodedKey.length();

		int encodedKeySearchIndex = 0;
		final int encodedKeySearchEnd = query.length() - (encodedKeyLength + 1);

		while (encodedKeySearchIndex <= encodedKeySearchEnd) {
			final int keyIndex = query.indexOf(encodedKey,
					encodedKeySearchIndex);
			if (keyIndex == -1) {
				break;
			}
			final int equalsIndex = keyIndex + encodedKeyLength;
			if (equalsIndex >= query.length()) {
				break;
			}
			if (query.charAt(equalsIndex) != '=') {
				encodedKeySearchIndex = equalsIndex + 1;
				continue;
			}
			if (keyIndex == 0 || query.charAt(keyIndex - 1) == '&') {
				int end = query.indexOf('&', equalsIndex);
				if (end == -1) {
					end = query.length();
				}
				try {
					return URLDecoder.decode(
							query.substring(equalsIndex + 1, end), HTTP.UTF_8);
				} catch (final UnsupportedEncodingException e) {
					// We never get here.
					return null;
				}
			} else {
				encodedKeySearchIndex = equalsIndex + 1;
			}
		}
		return null;
	}

	/**
	 * Searches the query string for parameter values with the given key.
	 * 
	 * @param key
	 *            which will be encoded
	 * 
	 * @throws UnsupportedOperationException
	 *             if this isn't a hierarchical URI
	 * @throws NullPointerException
	 *             if key is null
	 * 
	 * @return a list of decoded values
	 */
	public List<String> getQueryParameters(final Uri uri, final String key) {
		if (null == uri) {
			throw new NullPointerException("uri");
		}
		if (uri.isOpaque()) {
			throw new UnsupportedOperationException(NOT_HIERARCHICAL);
		}

		String query = uri.getEncodedQuery();
		if (query == null) {
			return Collections.emptyList();
		}

		String encodedKey;
		try {
			encodedKey = URLEncoder.encode(key, HTTP.UTF_8);
		} catch (final UnsupportedEncodingException e) {
			throw new AssertionError(e);
		}

		// Prepend query with "&" making the first parameter the same as the
		// rest.
		query = "&" + query;

		// Parameter prefix.
		final String prefix = "&" + encodedKey + "=";

		final ArrayList<String> values = new ArrayList<String>();

		int start = 0;
		final int length = query.length();
		while (start < length) {
			start = query.indexOf(prefix, start);

			if (start == -1) {
				// No more values.
				break;
			}

			// Move start to start of value.
			start += prefix.length();

			// Find end of value.
			int end = query.indexOf('&', start);
			if (end == -1) {
				end = query.length();
			}

			final String value = query.substring(start, end);
			try {
				values.add(URLDecoder.decode(value, HTTP.UTF_8));
			} catch (final UnsupportedEncodingException e) {
				throw new AssertionError(e);
			}

			start = end;
		}

		return Collections.unmodifiableList(values);
	}

	/**
	 * transform file bytes to string with unit
	 * 
	 * @param bytes
	 * @return
	 */
	public static String formatFileSize(final Context context, final long bytes) {
		return Formatter.formatFileSize(context, bytes);
	}

	/**
	 * transform file bytes to string with unit
	 * 
	 * @param bytes
	 * @return
	 */
	public static String formatFileSize2(final long bytes) {
		float displayValue = bytes;
		String unit = BYTES_UNIT;
		String format = SIZE_FORMAT_WITHOUT_DIGIT;
		if (bytes > GB_UNIT_THREDHOLD) {
			displayValue /= ONE_GB;
			unit = GB_UNIT;
			format = SIZE_FORMAT;
		} else if (bytes > MB_UNIT_THREDHOLD) {
			displayValue /= ONE_MB;
			unit = MB_UNIT;
			format = SIZE_FORMAT;
		} else if (bytes > KB_UNIT_THREDHOLD) {
			// For KB size, we don't need to display digits.
			displayValue = bytes / ONE_KB;
			unit = KB_UNIT;
			format = SIZE_FORMAT_WITHOUT_DIGIT;
		}
		return String.format(format, displayValue, unit);
	}

	/**
	 * orgainze application tags string to array
	 * 
	 * @param tagsInfo
	 * @return
	 */
	public static String[] parseTags(final String tagsInfo) {
		String[] tags = new String[] {};
		if (!TextUtils.isEmpty(tagsInfo)) {
			tags = ArrayUtils.normalize(tagsInfo.split(","));
		}
		return tags;
	}

	/**
	 * Check whether the email address is a valid email address.
	 * 
	 * @param email
	 *            the email address to check.
	 * @return true if the email address is valid, false otherwise.
	 */
	public static boolean isEmailValid(final String email) {
		if (TextUtils.isEmpty(email)) {
			return false;
		}
		final Matcher matcher = EMAIL_PATTERN.matcher(email);
		return matcher.matches();
	}

	/**
	 * Determine whether two objects are equal.
	 * 
	 * @param <T>
	 *            the type of the the objects.
	 * @param one
	 *            the first object.
	 * @param other
	 *            the second object.
	 */
	public static <T> boolean equals(final T one, final T other) {
		if (one != null && other != null) {
			return one.equals(other);
		}
		return one == other;
	}

	/**
	 * Determine whether two strings are equal.
	 * 
	 * @param one
	 *            the first object.
	 * @param other
	 *            the second object.
	 */
	public static boolean equals(final String one, final String other) {
		if (one != null && other != null) {
			return one.equals(other);
		}
		return one == other;
	}

	/**
	 * Determine whether two strings are equal, ignoring case.
	 * 
	 * @param one
	 *            the first object.
	 * @param other
	 *            the second object.
	 */
	public static boolean equalsIgnoreCase(final String one, final String other) {
		if (one != null && other != null) {
			return one.equalsIgnoreCase(other);
		}
		return one == other;
	}

	/**
	 * Determine whether a nick name is valid.
	 * 
	 * <p>
	 * A valid nick name must match the following rule:
	 * </p>
	 * <ul>
	 * <li>Have a length of 3-15.</li>
	 * <li>Contains only underscore(_), letters or digits.</li>
	 * <li>Digits can not be used as start of a nick name.</li>
	 * <li>Must not contains any reserved words below:</li>
	 * <ul>
	 * <li>Tapler</li>
	 * <li>Admin</li>
	 * <li>System</li>
	 * <li>MoboTap</li>
	 * <li>MoboSquare</li>
	 * </ul>
	 * </ul>
	 * 
	 * @param nicknName
	 *            the nick name to check.
	 * @return true if the nick name is valid, false otherwise.
	 */
	public static boolean isNickNameValid(final String nicknName) {
		return NICK_NAME_PATTERN.matcher(nicknName).matches()
				&& !NICK_NAME_RESERVED_PATTERN.matcher(nicknName).matches();
	}

	/**
	 * Determine whether a comment is valid.
	 * 
	 * <p>
	 * A valid comment must match the following rule:
	 * </p>
	 * <ul>
	 * <li>Have a length of 1-140.</li>
	 * </ul>
	 * 
	 * @param message
	 *            the content of the comment.
	 * @return true if the comment is valid, false otherwise.
	 */
	public static boolean isCommentValid(final String message) {
		return !TextUtils.isEmpty(message)
				&& message.length() <= MAX_COMMENT_LENGTH;
	}

	/**
	 * Retrieve string value of a {@linkplain CharSequence}.
	 * 
	 * @param value
	 *            the {@linkplain CharSequence} which value to retrieve.
	 * @return the value of the {@code value}.
	 */
	public static String valueOf(final CharSequence value) {
		if (null == value) {
			return null;
		}
		return value.toString();
	}

	public static byte[] streamToBytes(InputStream is) {
		ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
		byte[] buffer = new byte[1024];
		int len;
		try {
			while ((len = is.read(buffer)) >= 0) {
				os.write(buffer, 0, len);
			}
		} catch (java.io.IOException e) {
		}
		return os.toByteArray();
	}

}
